浅谈pycharm和终端下的ModuleNotFoundError 您所在的位置:网站首页 Pycharm终端显示PS而不显示虚拟环境名 浅谈pycharm和终端下的ModuleNotFoundError

浅谈pycharm和终端下的ModuleNotFoundError

2024-03-20 02:56| 来源: 网络整理| 查看: 265

        最近看到不少人都在询问,为啥pycharm下运行没问题,代码一放到终端下运行就报ModuleNotFoundError: No module named ‘****’ 错误。其实这是个很基础的设置问题,可能也是过于基础,很多人并没有那么关注,以致于出错时总是先责怪pycharm。下面我们就说简单梳理一下。

工作目录(working directory)

首先,这里先介绍一个概念。工作目录,所有计算机语言在运行时都会有一个本地磁盘工作主目录(工作空间),python也不例外。和大多数语言一样,代码的运行入口所在的py文件所属的目录被默认为项目的运行工作目录,在运行过程中,调用不同的代码模块或不同的包时,涉及路径问题,皆以工作主目录为基础。

具体的工作目录可以通过执行:os.getcwd()方法打印输出。

下面我们用一个简单的例子测试一下:

创建一个hello_dir的工程。工程下分别创建package1,package2这两个包,并创建名为main.py的代码文件。package1包创建名为code1.py的代码文件,package2包下创建名为code2.py的代码文件。

项目结构如下:

 

code1.py的代码内容:

#!/usr/bin/env python # -*- coding: utf-8 -*- import os print("code1:",os.getcwd())

 code2.py的代码内容:

#!/usr/bin/env python # -*- coding: utf-8 -*- import os print("code2:",os.getcwd())

 main.py的代码内容:

from package1 import code1 from package2 import code2

pycharm上直接运行main.py文件(不要疑惑代码只有两个from import也能运行),打印结果如下:

和预期结果是一致的,两个不同的包目录下打印出来的当前工作目录都是相同的,都是运行入口代码main.py所有的目录:E:\codes\hello_dir。

下面我换种玩法,换一个运行入口。把code1.py代码修改为如下:

#!/usr/bin/env python # -*- coding: utf-8 -*- import os from package2 import code2 #导入code2可以在加载code2时运行里面的方法 print("code1:",os.getcwd())

在pycharm上运行code1.py,结果如下:

从打印内容可以看到,当前工作主目录为E:\codes\hello_dir\package1,也就是说运行过程中调用到code2.py时,code2.py也是和code1.py一样在同一个主工作目录E:\codes\hello_dir\package1下运行的。

所以说,项目的工作目录决定于程序运行的入口代码文件所在的目录。

import模块或包

还是上面的代码,没做任何改变,换种运行方式,电脑上打开cmd,切换到目录E:\codes\hello_dir\package1,执行命令:python code1.py,结果如下:

看到这结果是不是很意外,刚才在pycharm上运行明明没有异常的。 这是为何?

这里先介绍两个方法,对后面理解这一问题有很大的帮助。

sys.modules :一个全局字典,该字典是python启动后就加载在内存中,存储所有加载到内存中的模块。每当导入新的模块,sys.modules都将记录这些模块。字典sys.modules对于加载模块起到了缓冲的作用。当某个模块第一次导入,字典sys.modules将自动记录该模块。当第二次再导入该模块时,python会直接到字典中查找,从而加快了程序运行的速度。

sys.path :是python启动时的搜索模块的路径集,是一个list,如果想添加额外的搜索目录路径,可以通过方法添加sys.path.append(path)。

Local命名空间:每个py文件都有一个独立的存储本py文件使用的变量名、方法名、模块名的变量。如果是模块,在添加到Local命名空间内前一般是先判断下在sys.modules有没有,如果有则直接把模块名放入Local命名空间,如果没有则先在sys.path里搜索相应模块文件,搜索到后加载到内存,前加入sys.modules,再把模块名称导入到当前py文件的Local命名空间里方可使用。

好了,言归正传,既然我们在终端命令行下运行时报了ModuleNotFoundError: No module named 'package2',我们在code1.py代码中修改一下,打印下package2到底有没有加载到内存中。

code1.py修改后:

#!/usr/bin/env python # -*- coding: utf-8 -*- import os,sys from package2 import code2 print("code1:",os.getcwd()) modules = sys.modules if "package2" in modules: print("package2 exists")

我们在终端命令行下运行一下: 

 还是一样的异常结果,我们可以看到包名package2 并没有被加载到内存中,在sys.modules中并没有找到,同时也意味着运行时的模块搜索路径中可能并没有搜索到路径package2 ,我们在code1.py代码加打印一下sys.path验证一下。

code1.py修改后:

#!/usr/bin/env python # -*- coding: utf-8 -*- import os,sys print("path:",sys.path) from package2 import code2 print("code1:",os.getcwd()) modules = sys.modules if "package2" in modules: print("package2 exists")

cmd中运行结果:

如上图红框所示便是程序在运行时搜索代码中导入的模块或包时的搜索路径,有package1(因为运行的主代码code1.py所属目录),但并没有package2目录,那运行时肯定就是找不到package2才导致报了异常。那这问题到底出在哪里?

这里比较有意思的是,以上在cmd上运行所出现的异常,如果切换回在pycharm中运行,异常便完全消失了,我们在pycharm中右键code1.py文件选择Run 'code1',结果如下:       

 两种不同的运行方式出现两种不同的结果,很多人开始怀疑是python环境逻辑问题或pycharm的问题,也有怀疑自己代码有问题的。废话不多说,下面我就来解释一下真正的缘由。

首先,在pycharm中运行代码也有两种常用的运行方式:1、利用工具栏提供的便捷运行按键或选中主代码后点选Run 'code1';2、pycharm底部点击“Terminal”功能选项打开一个类似cmd的终端窗口,敲打运行命令,如:python code1.py。

其次,前面我在运行时都会提到是在cmd还是在pycharm上运行,但在提到pycarm上运行时并没有提到具体是那种方式运行,很多人在用pycharm运行时习惯性选中主代码后点选Run 'code1'这种方式,熟不知,在使用pycharm自身提供的运行方式上,pycharm已经静悄悄地把package1上层目录E:\codes\hello_dir添加到sys.path搜索列表中了,所以运行时不会报异常。

所以,为了避免程序运行的“一致性”,我们在使用pycharm工具运行时尽量用pycharm提供的Terminal终端窗口来执行,这样在将代码部署到服务器上才能保证运行过程不发此类低级错误。

那么,针对以上报错ModuleNotFoundError: No module named 'package2',我们该怎么解决呢。在子目录packeage1运行时默认就只把当前目录添加到搜索路径列表sys.path中而已,并没有把其它依赖或关联的目录(如package2)添加到搜索路径列表中。

解决办法:

利用sys.path.append(path),把依赖或关联的模块所在路径添加到全局搜索路径列表sys.path中便可。

针对本方例子,可以这样加:

sys.path.append("package2所在绝对路径")

当然还可以更简单粗暴一点把项目目录E:\codes\hello_dir添加。

例如 code1.py添加自定义搜索路径后:

#!/usr/bin/env python # -*- coding: utf-8 -*- import os,sys print("path:",sys.path) sys.path.append(os.path.abspath(os.path.join(os.getcwd(), ".."))) print("path2:",sys.path) from package2 import code2 print("code1:",os.getcwd()) modules = sys.modules if "package2" in modules: print("package2 exists")

终端下运行结果:

这里大家可以留意下打印的两个sys.path的信息有何不同,自己加深理解一下。

最后的最后,还要提醒一下,py代码中import的顺序也很重要,就上面的代码code1.py,如果将

sys.path.append(os.path.abspath(os.path.join(os.getcwd(), ".."))) 这行放置在from package2 import code2 这行之后,那肯定也是报错的。

一般的,在py代码中import模块包时有个默认的先后顺序规范是:

python内置模块(标准模块)来自第三方的模块应用程序自定义模块

但python在运行加载模块时,在py代码中是从上往下的顺序加载的。除此外,还要根据实际情况灵活应用,如刚才所提。

减少低级错误的发生,在平时应该注意代码编写的规范,比如运行代码时,要习惯于先切换到主代码所在目录,再执行启动命令。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有